package hook

import (
	"reflect"
	"syscall"
	"unsafe"
)

// PageExecuteReadWrite 保护内存赋予读写权限
const PageExecuteReadWrite = 0x40

// win32方法
// VirtualProtect，是对应 Win32 函数的逻辑包装函数，它会在呼叫处理程序的虚拟位置空间里，变更认可页面区域上的保护。
var funcVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")

// Hook hook
func Hook(src interface{}, des interface{}) error {
	// 函数地址
	srcp := reflect.ValueOf(src).Pointer()
	desp := reflect.ValueOf(des).Pointer()
	// 计算公式：机器码=要跳转的地址-指令所在的位置-5
	delta := int64(desp-srcp) - 5
	jcode := []byte{
		0xe9,
		byte(delta),
		byte(delta >> 8),
		byte(delta >> 16),
		byte(delta >> 24),
	}
	// 修改内存写保护
	var oldPerms uint32 // 记录原保护级别，可以用来还原
	err := virtualProtect(srcp, 5, PageExecuteReadWrite, unsafe.Pointer(&oldPerms))
	if err != nil {
		panic(err)
	}
	// 替换
	srcData := getSlice(srcp, 5)
	copy(srcData, jcode)
	// 内存写保护还原
	return nil
}

// virtualProtect 修改受保护内存的读写权限
func virtualProtect(addr uintptr, size int, ptype uint32, oldPType unsafe.Pointer) error {
	ret, _, _ := funcVirtualProtect.Call(
		addr,
		uintptr(size),
		uintptr(ptype),
		uintptr(oldPType))
	if ret == 0 {
		return syscall.GetLastError()
	}
	return nil
}

// getSlice 读取指针指定长度的内容
func getSlice(p uintptr, length int) []byte {
	return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
		Data: p,
		Len:  length,
		Cap:  length,
	}))
}
